from urllib.parse import urlencode, parse_qs
from django.conf import settings
from urllib.request import urlopen
from .exceptions import QQAPIException
import json

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData
from . import constants

import logging

logger = logging.getLogger("django")


class OAuthQQ(object):
    """QQ登录的工具类"""

    def __init__(self, client_id=None, redirect_uri=None, state=None, client_secret=None):
        self.client_id = client_id or settings.QQ_CLIENT_ID
        self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI
        self.state = state or settings.QQ_STATE
        self.client_secret = client_secret or settings.QQ_CLIENT_SECRET

    def get_qq_login_url(self):
        """提供QQ扫码登录链接的"""

        # 准备url地址,自己补充?
        login_url = "https://graph.qq.com/oauth2.0/authorize?"

        # 准备参数
        params = {
            "response_type": "code",  # 固定值为code,告诉QQ服务器,扫码是为了得到code
            "client_id": self.client_id,  # 申请QQ登录成功后，分配给应用的appid
            "redirect_uri": self.redirect_uri,  # 扫码成功后跳转到的界面
            "state": self.state,  # QQ登录成功后回去的地方
            "scope": "get_user_info"  # 标识扫码最终为了获取QQ用户的信息(openid)
        }

        # 拼接QQ扫码登录链接
        login_url += urlencode(params)

        return login_url

    def get_access_token(self, code):
        """获取access token"""

        # 准备网址前半部分
        url = "https://graph.qq.com/oauth2.0/token?"

        # 准备参数
        params = {
            "grant_type": "authorization_code",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "code": code,
            "redirect_uri": self.redirect_uri
        }

        # 拼接获取access token的QQ服务器地址
        url += urlencode(params)

        # 使用code向QQ服务器发送请求获取access_token
        """
        urllib.request.urlopen(url, data=None):
        1.发送http请求,如果data为None,发送get请求;
                    如果data不为None,发送POST请求.
        2.返回response响应对象,可以通过read()读取响应体数据,
          需要注意读取出的响应体数据为bytes类型.
        """
        try:
            # 在开发API SDK的时候,只要有异常,自己千万别处理,全部都raise
            # 就是因为urlopen raise异常,我们才能捕获异常
            response = urlopen(url)
            # 通过read获取响应体数据,但该数据为bytes类型,需要转化成字典格式
            response_data = response.read()
            # bytes->查询字符串
            response_str = response_data.decode()
            # 查询字符串->字典
            response_dict = parse_qs(response_str)
            # 提取access_token
            # response_dict.get("access_token")获取的value值是一个列表:
            # {"access_token":"[FE04************************CCE2]"}
            access_token = response_dict.get("access_token")[0]
        except Exception as e:
            logger.error(e)
            raise QQAPIException("获取access_token失败")

        return access_token

    def get_openid(self, access_token):

        # 利用access_token拼接获取openid的QQ服务器地址,因为就一个参数,所以不用再准备参数字典和转化类型了.
        url = "https://graph.qq.com/oauth2.0/me?access_token=" + access_token

        response_str = ""

        try:
            # 使用access_token向QQ服务器获取openid
            response = urlopen(url)
            response_data = response.read()
            response_str = response_data.decode()
            """
            openid的返回包:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n,
            无规律,所以得用字符串切片的方式取到openid,parse_qs不管用了.
            """
            response_dict = json.loads(response_str[10:-4])
            # 获取openid
            openid = response_dict.get('openid')
        except Exception as e:
            # 如果有异常,QQ服务器返回"code=xxx&msg=xxx"
            data = parse_qs(response_str)
            logger.error(e)
            raise QQAPIException("code=%s msg=%s" % (data.get("code"), data.get("msg")))

        return openid

    @staticmethod
    def generate_save_user_token(openid):
        """
        生成保存用户数据的token
        :param openid:用户的openid
        :return: token
        """
        serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
        data = {"openid": openid}
        # .dumps,加密
        token = serializer.dumps(data)
        return token.decode()

    @staticmethod
    def check_save_user_token(token):
        """
        检验保存用户数据的token
        :param token: token
        :return: openid or None
        """
        serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
        try:
            # .loads,解密
            data = serializer.loads(token)
        except BadData:
            return None
        else:
            return data.get("openid")
